Перейти к основному содержимому

5.15. Lua и Luau

Разработчику Архитектору

О языке

Что такое Lua?

Lua — это компактный, быстрый, встраиваемый интерпретируемый язык программирования высокого уровня, разработанный с акцентом на простоту, гибкость и эффективность.

Lua — высокоуровневый язык программирования. Это означает, что он абстрагирован от деталей аппаратного обеспечения, предоставляет удобные структуры данных, автоматическое управление памятью и динамическую типизацию. Программисту не нужно заботиться о выделении и освобождении памяти вручную, работать с указателями или управлять регистрами процессора. Вместо этого он сосредоточен на логике программы, используя простой и понятный синтаксис.

Однако, в отличие от таких языков, как Python или Java, Lua не претендует на роль универсального «всё-в-одном» решения. Его сила — в лёгкости, скорости запуска и возможности интеграции. Поэтому, хотя он и высокоуровневый, он остаётся ближе к системному уровню по своей эффективности и компактности.

Он принадлежит к семейству скриптовых языков, но с уникальной особенностью: он изначально задумывался как встраиваемый язык (embedded language). Это ключевое отличие. Большинство скриптовых языков (например, Python, JavaScript) используются как автономные среды выполнения, тогда как Lua проектировался, чтобы жить внутри другого приложения, будь то игра, роутер, база данных или промышленное ПО.

Его синтаксис и архитектура во многом напоминают Scheme (язык из семейства Lisp), особенно в плане функциональных возможностей и минимализма. Однако по стилю и практическому применению он ближе к Python — благодаря читаемости, динамической типизации и интерактивной разработке. Тем не менее, в отличие от Python, Lua не стремится быть «универсальным инструментом», а скорее — «гибким клеем» между компонентами системы.

Lua — мультипарадигменный язык, но он не навязывает ни одну конкретную модель. Он поддерживает:

  • Процедурное программирование — как основу.
  • Функциональное программирование — функции являются объектами первого класса, поддерживаются замыкания, высшие функции.
  • Прототипное ООП — через таблицы и метатаблицы, без классов «из коробки», но с возможностью эмуляции классов, наследования, полиморфизма.
  • Императивное программирование — стандартные циклы, условия, присваивания.

Lua не является строго объектно-ориентированным, как Java или C#, и не является чисто функциональным, как Haskell. Он предоставляет минимальный набор примитивов, которые позволяют программисту самому построить нужную парадигму — будь то классы, модули, события или конечные автоматы.

Lua — один из самых распространённых встраиваемых скриптовых языков в мире. Конечно, всегда сначала называют именно игры, к примеру, Roblox, World Of Warcraft, Angry Birds, Civilization. Также можно встретить в системах и сетевых устройствах, роутерах, IoT, редакторах и инструментах разработки вроде Adobe Lightroom.

Почему он популярен? По нескольким причинам:

  1. Минимализм. Ядро Lua весит всего 20-25 КБ. Это один из самых компактных языков с полной реализацией.
  2. Встраиваемость. Lua легко интегрируется с C/C++ через простой API. Приложение может вызывать Lua-функции и наоборот.
  3. Скорость. Интерпретатор Lua очень быстрый, особенно при использовании LuaJIT (Just-In-Time компилятор).
  4. Гибкость. Отсутствует жёсткая структура, что позволяет адаптировать язык под любую задачу - от ООП до DSL (предметно-ориентированных языков).
  5. Переносимость. Lua написан на чистом ANSI C, компилируется почти на любой платформе.
  6. Открытость и простота реализации. Исходный код Lua прозрачен, хорошо документирован и часто используется для обучения и как пример парсера и интерпретатора.

Lua — это интерпретируемый язык, но с важным уточнением: он использует двухэтапный процесс выполнения, включающий компиляцию в байт-код и последующую интерпретацию этого байт-кода на виртуальной машине (VM).

Это означает, что когда вы запускаете Lua-скрипт, исходный код не читается напрямую построчно, как в простейших интерпретаторах. Вместо этого он сначала скомпилируется в промежуточное представление — байт-код, который затем выполняется на виртуальной машине Lua:

  1. Компиляция в байт-код;
  2. Интерпретация байт-кода на виртуальной машине.

Это не JIT-компиляция по умолчанию (кроме случаев с LuaJIT), но даже без JIT производительность остаётся высокой благодаря эффективности VM.

Разберём пример.

Шаг 1. Исходный код превращается в байт-код.

Вы пишете исходный код, и сохраняете в файл с расширением .lua.

Когда вы запускаете скрипт (например, через lua script.lua), Lua-интерпретатор сначала парсит ваш .lua-файл:

print("Hello")
x = 10 + 20

Парсер строит абстрактное синтаксическое дерево (AST), а затем компилятор Lua преобразует его в байт-код — низкоуровневые инструкции, понятные виртуальной машине Lua. Этот байт-код сохраняется в памяти или может быть записан в файл (например, .luac) с помощью утилиты luac.

Важно отметить, что формат байт-кода специфичен для версии Lua. Байт-код Lua 5.1 несовместим с Lua 5.4.

Шаг 2. Выполнение байт-кода на виртуальной машине. Виртуальная машина Lua - это регистровая виртуальная машина (в отличие от стековых, например JVM (Java) или CPython). Потому язык и производительный.

Регистровая архитектура подразумевает, что операции работают с «регистрами», условными ячейками памяти, а не с явным стеком. Это снижает количество инструкций и увеличивает скорость.

VM пошагово читает байт-код и выполняет инструкции, организуя цикл выборки-выполнения (fetch-execute cycle).

Параллельно работает сборщик мусора (mark & sweep), освобождая память от недостижимых объектов.

Каждая инструкция байт-кода обрабатывается ВМ, которая взаимодействует с элементами языка - таблицами, функциями, строками, числами, корутинами, метатаблицами.

image.png

Всё это происходит в рамках одного процесса.

Lua VM, виртуальная машина - часть приложения, а не отдельный процесс.

Сначала человек пишет исходный код.

Lua парсит и переводит его в байт-код.

Байт-код интерпретируется построчно силами ВМ, и переводится в машинный код, который отправляется в процессор для выполнения инструкций.

Так и работает уровень:

  • Высокий (человекочитаемый);
  • Средний (уровень ВМ);
  • Низкий (машиночитаемый).

Стандартный интерпретатор Lua можно называть просто Lua, PUC-Rio или «обычный Lua». Это чистый интерпретатор байт-кода. Но существует популярная альтернатива — LuaJIT (Just-In-Time Compiler).

LuaJIT технически занимает то же место в схеме, но компилирует горячие участки кода (часто выполняемые циклы, функции) в нативный машинный код во время выполнения, что ускоряет выполнение в 2-10 раз.

Важно понимать то, что Lua взаимодействует с C (Си), так как написан на чистом ANSI C, и его виртуальная машина легко встраивается в любое C/C++ приложение. Через C API хост-приложение может управлять жизненным циклом Lua-состояния, загружать и выполнять Lua-скрипты, вызывать Lua-функции из C, регистрировать C-функции, доступные из Lua. К примеру, движок игры на C++ вызывает lua_pcall, чтобы запустить скрипт поведения персонажа, написанный на Lua. Таким образом, Lua не генерирует машинный код сам по себе, но через C API и host-приложение его логика в конечном счёте исполняется процессором как часть родительской программы.